#ifndef	_CG_UTILS_
#define _CG_UTILS_

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
#include <Cg/cg.h>
#include <Cg/cgGL.h>

/* Cg global variables */
CGcontext	myCgContext;
CGeffect	myCgEffect;
CGtechnique	myCgTechnique;
CGparameter	myCgEyePositionParam;
CGparameter	myCgLightPositionParam;
CGparameter	myCgModelViewProjParam;

static const char *myProgramName = "cgfx_bumpdemo";

static void checkForCgError(const char *situation)
{
	CGerror error;
	const char *string = cgGetLastErrorString(&error);

	if (error != CG_NO_ERROR) {
		if (error == CG_COMPILER_ERROR) {
			fprintf(stderr,
				"Program: %s\n"
				"Situation: %s\n"
				"Error: %s\n\n"
				"Cg compiler output...\n%s",
				myProgramName, situation, string,
				cgGetLastListing(myCgContext));
		} else {
			fprintf(stderr,
				"Program: %s\n"
				"Situation: %s\n"
				"Error: %s",
				myProgramName, situation, string);
		}
		exit(1);
	}
}

static void initCgFX(void)
{
	myCgContext = cgCreateContext();
	checkForCgError("creating context");
	cgGLRegisterStates(myCgContext);
	checkForCgError("registering standard CgFX states");
	cgGLSetManageTextureParameters(myCgContext, CG_TRUE);
	checkForCgError("manage texture parameters");

	myCgEffect = cgCreateEffectFromFile(myCgContext, "cg_shader/bumpdemo.cgfx", NULL);
	checkForCgError("creating bumpdemo.cgfx effect");
	assert(myCgEffect);

	myCgTechnique = cgGetFirstTechnique(myCgEffect);
	while (myCgTechnique && cgValidateTechnique(myCgTechnique) == CG_FALSE)
	{
		fprintf(stderr, "%s: Technique %s did not validate. Skipping.\n", 
			myProgramName, cgGetTechniqueName(myCgTechnique));
		myCgTechnique = cgGetNextTechnique(myCgTechnique);
	}
	if (myCgTechnique)
	{
		fprintf(stderr, "%s: Use technique %s.\n",
			myProgramName, cgGetTechniqueName(myCgTechnique));
	} else
	{
		fprintf(stderr, "%s: No valid technique\n", myProgramName);
		exit(1);
	}

	myCgModelViewProjParam = cgGetEffectParameterBySemantic(myCgEffect, "ModelViewProjection");
	if (!myCgModelViewProjParam)
	{
		fprintf(stderr, "%s: mus find parameter with ModelViewProjection semantic\n", myProgramName);
		exit(1);
	}
	myCgEyePositionParam =
		cgGetNamedEffectParameter(myCgEffect, "EyePosition");
	if (!myCgEyePositionParam) {
		fprintf(stderr, "%s: must find parameter named EyePosition\n",
			myProgramName);
		exit(1);
	}
	myCgLightPositionParam =
		cgGetNamedEffectParameter(myCgEffect, "LightPosition");
	if (!myCgLightPositionParam) {
		fprintf(stderr, "%s: must find parameter named LightPosition\n",
			myProgramName);
		exit(1);
	}
}

static const GLubyte
myBrickNormalMapImage[3*(128*128+64*64+32*32+16*16+8*8+4*4+2*2+1*1)] = {
	/* RGB8 image data for a mipmapped 128x128 normal map for a brick pattern */
#include "texture/brick_image.h"
};

static const GLubyte
myNormalizeVectorCubeMapImage[6*3*32*32] = {
	/* RGB8 image data for a normalization vector cube map with 32x32 faces */
#include "texture/normcm_image.h"
};

static void useSamplerParameter(CGeffect effect,
								const char *paramName, GLuint texobj)
{
	CGparameter param = cgGetNamedEffectParameter(effect, paramName);

	if (!param) {
		fprintf(stderr, "%s: expected effect parameter named %s\n",
			myProgramName, paramName);
		exit(1);
	}
	cgGLSetTextureParameter(param, texobj);
	cgSetSamplerState(param);

	checkForCgError("use sampler parameter");
}

/* OpenGL texture object (TO) handles. */
enum {
	TO_NORMALIZE_VECTOR_CUBE_MAP = 1,
	TO_PRECOMPUTED_INTERS = 2,
};

static void initOpenGL(void)
{
	int face;
	const GLubyte *image;

	glClearColor(0.1, 0.3, 0.6, 0.0);
	glEnable(GL_DEPTH_TEST);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	glBindTexture(GL_TEXTURE_2D, TO_PRECOMPUTED_INTERS);
	useSamplerParameter(myCgEffect, "normalMap", TO_PRECOMPUTED_INTERS);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 128, 128, 0, GL_RGB, GL_UNSIGNED_BYTE, myBrickNormalMapImage);

	glBindTexture(GL_TEXTURE_CUBE_MAP, TO_NORMALIZE_VECTOR_CUBE_MAP);
	for (face = 0, image = myNormalizeVectorCubeMapImage;
		face < 6;
		face++, image += 3 * 32 * 32)
	{
		glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_RGB8, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE,
			image);
	}
	useSamplerParameter(myCgEffect, "normalizeCube", TO_NORMALIZE_VECTOR_CUBE_MAP);
}

static void drawFlatPatch(float rows, float columns)
{
	float m = 1.0f / columns, n = 1.0f / rows;
	int i,j;
	for (i = 0; i < columns; i++)
	{
		glBegin(GL_QUAD_STRIP);
		for (j = 0; j <= rows; j++)
		{
			glVertex2f(i * m , j * n);
			glVertex2f((i + 1) * m, j * n);
		}
		glVertex2f(i * m, 0);
		glVertex2f((i + 1) * m, 0);
		glEnd();
	}
}

static int myAnimating = 0;
static float myEyeAngle = 0;
static const float myLightPosition[3] = {-8, 0, 15};

static void display(void)
{
	const float eyeRadius = 18.0,
		eyeElevationRange = 8.0;
	const int slides = 20, rings = 40;
	float eyePosition[3];

	eyePosition[0] = eyeRadius * sin(myEyeAngle);
	eyePosition[1] = eyeElevationRange * sin(myEyeAngle);
	eyePosition[2] = eyeRadius * cos(myEyeAngle);

	CGpass pass;

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	gluLookAt(eyePosition[0], eyePosition[1], eyePosition[2], 0.f, 0.f, 0.f, 0.f, 1.f, 0.f);

	cgGLSetStateMatrixParameter(myCgModelViewProjParam, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
	cgGLSetParameter3fv(myCgEyePositionParam, eyePosition);
	cgGLSetParameter3fv(myCgLightPositionParam, myLightPosition);

	pass = cgGetFirstPass(myCgTechnique);
	while (pass)
	{
		cgSetPassState(pass);
		drawFlatPatch(slides, rings);
		cgResetPassState(pass);
		pass = cgGetNextPass(pass);
	}

	glutSwapBuffers();
}
#endif	//_CG_UTILS_